package container;

import java.util.*;

/**
 * https://blog.csdn.net/f641385712/article/details/81981095
 */
public class T02_Collections {

    static List<Integer> list = new ArrayList<>();

    static {
        list.add(12);
        list.add(-15);
        list.add(7);
        list.add(4);
        list.add(35);
        list.add(9);
    }

    public static void main(String[] args) {
        // 将所有指定元素添加到指定 collection 中
        Collections.addAll(new ArrayList<>(), list);
        // 将所有元素从一个列表复制到另一个列表
        List dest = Arrays.asList(new Integer[list.size()]);
        Collections.copy(dest, list);
        // 如果两个指定 collection 中没有相同的元素，则返回 true （判断两个集合是否有重合的元素）
        Collections.disjoint(Collections.singletonList(12), list);
    }

    /**
     * 返回由指定对象的 n 个副本组成的不可变列表（可以 copy）
     */
    public static void nCopies() {
        // 若需要生成相同元素，可用这个快速生成
        List<Integer> list = Collections.nCopies(5, new Integer(1));
        System.out.println(list); // [1, 1, 1, 1, 1]

        // 注意，这里返回 true，所以需要注意：长度虽然是 5，但是都是副本的拷贝，变一个其余都变的
        System.out.println(list.size());
        System.out.println(list.get(0) == list.get(1)); // true
    }

    /**
     * checkedCollection、checkedList 等等：返回类型检查的集合，更安全了。
     * 备注：现在都使用泛型集合了，不太建议使用了。当然 MyBatis 里存在一些问题，可以考虑使用
     */
    public static void checkedCollection() {
        // 这样子，下面程序不抱错，显然是纯在安全隐患的
        // List<String> list = Arrays.asList("12","23");
        // List obj = list;
        // list 中存在了一个非String类型，程序执行到这里不会报错
        // obj.add(112);

        List<String> list = Arrays.asList("12", "23");
        List<String> safeList = Collections.checkedList(list, String.class);
        List obj = safeList;
        // 检查容器视图受限于虚拟机可运行的运行时检查，这里就报错了
        obj.add(new Date());// 只有执行到这一步才会抛出 java.lang.ClassCastException
    }

    /**
     * 以后进先出(Lifo) Queue 的形式返回某个 Deque 的视图
     * Deque 是接口，具体继承关系为：Collection –> Queue –> Deque –> LinkedList、ArrayDeque、LinkedBlockingDeque
     * Queue（队列）接口与 List、Set 同一级别，都是继承了 Collection 接口。
     * Queue 使用时要尽量避免 Collection 的 add() 和 remove()方法，而是要使用 offer() 来加入元素，使用 poll() 来获取并移出元素。
     * 它们的优点是通过返回值可以判断成功与否，add() 和 remove() 方法在失败的时候会抛出异常。如果要使用前端而不移出该元素，使用 element() 或者 peek() 方法。
     * Deque（双端队列）接口支持两端插入和移除元素。名称 deque 是“double ended queue（双端队列）”的缩写，通常读为 “deck”。
     */
    public static void asLifoQueue() {
        // LinkedList 是先进先出的，First-in-first-out fifo
        LinkedList<Integer> llist1 = new LinkedList<>(Arrays.asList(2, 4, 6));
        System.out.println(llist1); //[2, 4, 6]

        // offer 方法底层调用 add 方法，一模一样的
        System.out.println(llist1.add(5)); // true，添加成功返回的 true
        System.out.println(llist1); // [2, 4, 6, 5]，发现添加到尾部的

        //element 和 peek 都是把队列首部元素取出，但是不移除，poll() 方法也是取出，但是会移除
        System.out.println(llist1.element()); // 2
        System.out.println(llist1.peek()); // 2
        System.out.println(llist1); // [2, 4, 6, 5, 9]

        System.out.println("-------------------------");

        // 变成 Last-in-first-out  lifo 的队列，也就是后进先出（上面 LinkedList 是先进先出，需要注意）
        Queue q = Collections.asLifoQueue(llist1);
        System.out.println(q);

        System.out.println(q.add(5)); // true
        System.out.println(q); // [5, 2, 4, 6, 5]
    }

    /**
     * 以前我们要初始化一个元素，到 List，我们经常这么用：Arrays.asList()，那么今后，若只有一个元素需要构建 list，请使用 singletonXXX
     * singletonXXX 优势分析：
     * 这个方法主要用于只有一个元素的优化，减少内存分配，无需分配额外的内存，可以从 SingletonList 内部类看得出来,由于只有一个 element，因此可
     * 以做到内存分配最小化，相比之下 ArrayList 的 DEFAULT_CAPACITY = 10 个
     * 同样需要注意的是：它们返回都是只读的 List，如果调用修改接口，将会抛出 UnsupportedOperationException
     */
    public static void singleton() {
        List<String> list = Collections.singletonList("singleton");
    }

    /**
     * 同步和只读，这个使用起来比较简单，但是能提高效率和安全性。特别是只读的，建议多使用
     * SynchronizedList()、SynchronizedSet()、unmodifiableCollection() 等等
     */
    public static void unmodifiable() {
        Collection<Integer> integers = Collections.unmodifiableCollection(list);
    }

    /**
     * 查找及替换操作
     */
    public static void replace() {
        System.out.println("源列表：" + list);

        // 最大值
        System.out.println("最大值：" + Collections.max(list)); // 35

        // 最小值
        System.out.println("最小值：" + Collections.min(list)); // -15

        // 替换，把所有的 -15 替换成 12
        Collections.replaceAll(list, -15, 12);
        System.out.println("-15 替换 12：" + list); // [12, 12, 7, 4, 35, 9]

        // 出现次数(这个特别有用，某个元素出现的次数)，支持找 null 出现的次数
        System.out.println("12 出现次数：" + Collections.frequency(list, 12)); // 2

        // 排序，因为二分查找必须有序，否则可能不准
        Collections.sort(list);
        System.out.println("排序后：" + list); // [4, 7, 9, 12, 12, 35]

        // 二分查找（注意，返回的是查找对象的索引，List必须是有序的）
        System.out.println("-15 下标：" + Collections.binarySearch(list, -15)); // -1 没找到就是 -1
        System.out.println("7 下标：" + Collections.binarySearch(list, 7)); // 1
        System.out.println("35 下标：" + Collections.binarySearch(list, 35)); // 5

        // indexOfSubList：返回指定源列表中第一次出现指定目标列表的起始位置；如果没有出现这样的列表，则返回 -1。
        // lastIndexOfSubList：同上(最后一次)，若出现多次的请，会有差异
        System.out.println(Collections.indexOfSubList(list, Arrays.asList(7, 9))); // 1
        System.out.println(Collections.lastIndexOfSubList(list, Arrays.asList(7, 9))); // 1

        // fill：用后面元素，把所有元素都给替换掉
        Collections.fill(list, 1);
        System.out.println("fill 后的：" + list); // [1, 1, 1, 1, 1, 1]
    }

    /**
     * swap 交换两个位置的值
     */
    public static void swap() {
        System.out.println("swap之前:" + list);
        Collections.swap(list, 0, 2); //把0位置元素和2位置交换（备注：若是不可变集合，此处是会抛错的）
        System.out.println("swap之后:" + list);
    }

    public static void sort() {
        System.out.println("源列表：" + list); // [12, -15, 7, 4, 35, 9]

        // 排序（自然顺序）
        Collections.sort(list);
        System.out.println("自然序：" + list); // [-15, 4, 7, 9, 12, 35]

        // 逆序
        Collections.reverse(list);
        System.out.println("逆序：" + list); // [35, 12, 9, 7, 4, -15]


        // 随机排序(扑克牌洗牌经常使用) 每次结果都不一样
        Collections.shuffle(list);
        System.out.println("随机序：" + list); // [9, 4, -15, 12, 7, 35]

        // 定制排序的用法(根据指定比较器产生的顺序对指定列表进行排序)，将 int 类型转成 string 进行比较（注意，不再是数字了，排序有变化）
        Collections.sort(list, Comparator.comparing(String::valueOf));
        System.out.println("定制序：" + list); // [-15, 12, 35, 4, 7, 9]

        // 旋转（旋转效果有点意思）
        Collections.rotate(list, 3); // [4, 7, 9, -15, 12, 35]
        System.out.println("旋转3：" + list);

        Collections.rotate(list, -3);
        System.out.println("旋转-3：" + list); // [-15, 12, 35, 4, 7, 9]
    }
}
